package com.cmc.cloud.cmclink.doc.service.impl.manifest;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.stream.Collectors;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.collections.CollectionUtils;
import org.apache.tika.utils.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import org.springframework.validation.annotation.Validated;

import com.cmc.cloud.cmclink.bdt.api.base.dto.req.PortListByCodeReqDTO;
import com.cmc.cloud.cmclink.bdt.api.base.dto.resp.PortListByCodeRespDTO;
import com.cmc.cloud.cmclink.bdt.api.base.dto.resp.VesselNameDataRespDTO;
import com.cmc.cloud.cmclink.common.report.handler.ReportUrlHandler;
import com.cmc.cloud.cmclink.doc.api.document.emums.*;
import com.cmc.cloud.cmclink.doc.api.document.req.cargomanifest.*;
import com.cmc.cloud.cmclink.doc.constants.CommonConstants;
import com.cmc.cloud.cmclink.doc.constants.errorconstant.ArrivalNoticeErrorCodeConstants;
import com.cmc.cloud.cmclink.doc.constants.errorconstant.CargoManifestErrorCodeConstants;
import com.cmc.cloud.cmclink.doc.convert.CargoManifestConvert;
import com.cmc.cloud.cmclink.doc.entity.*;
import com.cmc.cloud.cmclink.doc.mapper.BlnoCntrMapper;
import com.cmc.cloud.cmclink.doc.mapper.BlnoFreightMapper;
import com.cmc.cloud.cmclink.doc.mapper.CargoManifestMapper;
import com.cmc.cloud.cmclink.doc.rpcservice.PortService;
import com.cmc.cloud.cmclink.doc.rpcservice.VesselTransferService;
import com.cmc.cloud.cmclink.doc.service.BlnoCntrService;
import com.cmc.cloud.cmclink.doc.service.VgmService;
import com.cmc.cloud.cmclink.doc.service.manifest.CargoManifestService;
import com.cmc.cloud.cmclink.doc.service.si.BlnoCargoService;
import com.cmc.cloud.cmclink.doc.service.si.BlnoFreightService;
import com.cmc.cloud.cmclink.doc.service.si.BlnoScnService;
import com.cmc.cloud.cmclink.doc.service.si.BlnoVslvoyService;
import com.cmc.cloud.cmclink.doc.util.CostChargeUtil;
import com.cmc.cloud.cmclink.doc.util.DateUtils;
import com.cmc.cloud.cmclink.doc.util.FileOperateUtil;
import com.cmc.cloud.cmclink.doc.vo.manifestvo.cargomanifest.CargoManifestQueryReqVO;
import com.cmc.cloud.cmclink.doc.vo.manifestvo.cargomanifest.CargoManifestQueryRespVO;
import com.cmc.cloud.cmclink.doc.vo.vgm.VgmQueryInfoVO;
import com.cmc.cloud.cmclink.svl.api.base.resp.VesselVoyRespDTO;
import com.cmes.framework.common.exception.util.ServiceExceptionUtil;
import com.cmes.framework.mybatis.core.query.LambdaQueryWrapperX;

import lombok.extern.slf4j.Slf4j;

/**
 * 舱单货物信息 Service 实现类
 *
 * @author dev89
 */
@Service
@Validated
@Slf4j
public class CargoManifestServiceImpl implements CargoManifestService {

    private static final String REPORT_TIME_PATTERN = "yyyy/M/d HH:mm";
    private static final String SINOLINES = "SINOLINES";

    /**
     * 构造url参数
     */
    private static final String STRING_PARAM_URL =
        "&currentPort=%s&vesselCode=%s&voyage=%s&blNo=%s&exImType=%s&source=%s";

    /**
     * 构造url参数
     */
    private static final String BOOLEAN_PARAM_URL =
        "&showFreight=%b&cancelEmptyContainer=%b&sortByPod=%b&includeAllChange=%b&replaceScnSinolines=%b";

    /**
     * 构造url参数
     */
    private static final String SET_PARAM_URL =
        "&cargoTypes=%s&cntrStatuses=%s&cntrTypes=%s&payments=%s&bkpCodes=%s&blStatuses=%s&porCodes=%s&polCodes=%s&podCodes=%s&delCodes=%s&spaceOwners=%s&cntrOwners=%s&blNoIds=%s";

    @Value("${cargo.manifest.download.url}")
    private String downloadUrl;

    @Value("${spring.profiles.active:dev}")
    private String activeProfile;

    @Resource
    private CargoManifestMapper cargoManifestMapper;
    @Resource
    private BlnoCntrService blnoCntrService;
    @Resource
    private BlnoCargoService blnoCargoService;
    @Resource
    private BlnoFreightService blnoFreightService;
    @Resource
    private BlnoVslvoyService blnoVslvoyService;

    @Resource
    private CostChargeUtil costChargeUtil;

    @Resource
    private VesselTransferService vesselTransferService;

    @Resource
    private BlnoCntrMapper blnoCntrMapper;

    @Resource
    private BlnoFreightMapper blnoFreightMapper;

    @Resource
    private BlnoScnService blnoScnService;

    @Resource
    private VgmService vgmService;

    @Resource
    private PortService portService;

    @Resource
    private ReportUrlHandler reportUrlHandler;

    @Override
    public CargoManifestQueryRespVO queryCargoManifest(CargoManifestQueryReqVO reqVO) {
        CargoManifestReqDTO reqDto = CargoManifestConvert.INSTANCE.convert(reqVO);
        checkQueryReqVo(reqDto);
        String manifestSource = reqVO.getSource();
        if (StringUtils.isEmpty(manifestSource)
            || CargoManifestSourceEnum.SI.getValue().equalsIgnoreCase(manifestSource)) {
            return queryCargoManifestSi(reqDto);
        } else {
            return queryCargoManifestManifest(reqVO);
        }
    }

    @Override
    public CargoManifestRespDTO queryFilterCargoManifest(CargoManifestReqDTO reqDto) {
        checkQueryReqVo(reqDto);
        List<BlnoDO> blnoDoList = queryBlnoDo(reqDto);
        if (CollectionUtils.isEmpty(blnoDoList)) {
            log.error("query cargo manifest resp null");
            return new CargoManifestRespDTO();
        }
        Set<Long> ids = blnoDoList.stream().map(BlnoDO::getId).collect(Collectors.toSet());
        CargoManifestRespDTO respDto = new CargoManifestRespDTO();
        setReportHeader(respDto, reqDto, blnoDoList);

        List<BlnoCntrDO> cntrDOList = getBlnoCntrByBlnoIds(ids, reqDto.getCntrTypes(), reqDto.getCntrOwners());
        List<BlnoFreightDO> blnoFreightDOList = getBlnoFreightByBlnoIds(ids, reqDto);
        List<BlnoCargoDO> blnoCargoDOList = blnoCargoService.getBlnoCargoByBlnoIds(ids);
        List<BlnoScnDO> blnoScnDOList = blnoScnService.getBlnoScnByBlnoIdList(ids);
        Map<Long, List<BlnoCntrDO>> cntrListMap =
            cntrDOList.stream().collect(Collectors.groupingBy(BlnoCntrDO::getBlnoId));
        Map<Long, List<BlnoFreightDO>> freightListMap =
            blnoFreightDOList.stream().collect(Collectors.groupingBy(BlnoFreightDO::getBlnoId));
        Map<Long, List<BlnoCargoDO>> cargoListMap =
            blnoCargoDOList.stream().collect(Collectors.groupingBy(BlnoCargoDO::getBlnoId));
        Map<Long, List<BlnoScnDO>> scnListMap =
            blnoScnDOList.stream().collect(Collectors.groupingBy(BlnoScnDO::getBlnoId));
        List<CargoManifestInfo> cargoManifestInfoList = new ArrayList<>();
        for (BlnoDO blnoDo : blnoDoList) {
            CargoManifestInfo cargoManifestInfo = new CargoManifestInfo();
            List<BlnoCntrDO> cntrList = cntrListMap.getOrDefault(blnoDo.getId(), new ArrayList<>());
            List<BlnoFreightDO> freightList = freightListMap.getOrDefault(blnoDo.getId(), new ArrayList<>());
            List<BlnoScnDO> scnList = scnListMap.getOrDefault(blnoDo.getId(), new ArrayList<>());
            BlnoCargoDO cargo = cargoListMap.get(blnoDo.getId()).get(0);
            setReportBody(reqDto, cargoManifestInfo, blnoDo, cntrList, freightList, scnList, cargo);
            cargoManifestInfoList.add(cargoManifestInfo);
        }
        respDto.setCargoManifestInfoList(cargoManifestInfoList);

        // 设置合计行
        SummaryRowInfo summaryRowInfo = buildSummaryRowInfo(ids.size(), cntrDOList, blnoCargoDOList);
        respDto.setSummaryRowInfo(summaryRowInfo);
        return respDto;
    }

    @Override
    public void downloadCargoManifest(HttpServletResponse response, CargoManifestReqDTO reqVO) {
        String generateUrl;
        try {
            generateUrl = reportUrlHandler.handle(downloadUrl);
        } catch (Exception e) {
            log.error("generateUrl failed", e);
            throw ServiceExceptionUtil.exception(ArrivalNoticeErrorCodeConstants.ARRIVAL_NOTICE_URL_FAILED);
        }

        String downloadUrl = getDownloadUrl(reqVO, generateUrl);
        downLoadFile(response, downloadUrl, reqVO.getType());
    }

    private SummaryRowInfo buildSummaryRowInfo(int blNumber, List<BlnoCntrDO> cntrDOList,
        List<BlnoCargoDO> blnoCargoDOList) {
        SummaryRowInfo summaryRowInfo = new SummaryRowInfo();
        summaryRowInfo.setBlNumber(blNumber);
        int smallCntrNumber = 0;
        int bigCntrNumber = 0;
        for (BlnoCntrDO cntr : cntrDOList) {
            if (cntr.getCntrSize().equals(CommonConstants.TWENTY)) {
                smallCntrNumber++;
            } else {
                bigCntrNumber++;
            }
        }
        summaryRowInfo.setSmallCntrNumber(smallCntrNumber);
        summaryRowInfo.setBigCntrNumber(bigCntrNumber);

        BigDecimal grossWeightTotal = new BigDecimal(0);
        for (BlnoCargoDO cargo : blnoCargoDOList) {
            grossWeightTotal = grossWeightTotal.add(cargo.getGrossWeight());
        }
        summaryRowInfo.setGrossWeightTotal(grossWeightTotal);
        return summaryRowInfo;
    }

    private String getDownloadUrl(CargoManifestReqDTO reqVO, String generateUrl) {
        String currentPort = reqVO.getCurrentPort() == null ? "" : reqVO.getCurrentPort();
        String vesselCode = reqVO.getVesselCode() == null ? "" : reqVO.getVesselCode();
        String voyage = reqVO.getVoyage() == null ? "" : reqVO.getVoyage();
        String blNo = reqVO.getBlNo() == null ? "" : reqVO.getBlNo();
        String exImType = reqVO.getExImType() == null ? "" : reqVO.getExImType();
        String source = reqVO.getSource() == null ? "" : reqVO.getSource();
        String strParamUrl = String.format(STRING_PARAM_URL, currentPort, vesselCode, voyage, blNo, exImType, source);
        String booleanParamUrl =
            String.format(BOOLEAN_PARAM_URL, reqVO.getShowFreight(), reqVO.getCancelEmptyContainer(),
                reqVO.getSortByPod(), reqVO.getIncludeAllChange(), reqVO.getReplaceScnSinolines());
        Set<String> cargoTypes = reqVO.getCargoTypes() == null ? new HashSet<>() : reqVO.getCargoTypes();
        Set<String> cntrStatuses = reqVO.getCntrStatuses() == null ? new HashSet<>() : reqVO.getCntrStatuses();
        Set<String> cntrTypes = reqVO.getCntrTypes() == null ? new HashSet<>() : reqVO.getCntrTypes();
        Set<String> payments = reqVO.getPayments() == null ? new HashSet<>() : reqVO.getPayments();
        Set<String> bkpCodes = reqVO.getBkpCodes() == null ? new HashSet<>() : reqVO.getBkpCodes();
        Set<String> blStatuses = reqVO.getBlStatuses() == null ? new HashSet<>() : reqVO.getBlStatuses();
        Set<String> porCodes = reqVO.getPorCodes() == null ? new HashSet<>() : reqVO.getPorCodes();
        Set<String> polCodes = reqVO.getPolCodes() == null ? new HashSet<>() : reqVO.getPolCodes();
        Set<String> podCodes = reqVO.getPodCodes() == null ? new HashSet<>() : reqVO.getPodCodes();
        Set<String> delCodes = reqVO.getDelCodes() == null ? new HashSet<>() : reqVO.getDelCodes();
        Set<String> spaceOwners = reqVO.getSpaceOwners() == null ? new HashSet<>() : reqVO.getSpaceOwners();
        Set<String> cntrOwners = reqVO.getCntrOwners() == null ? new HashSet<>() : reqVO.getCntrOwners();
        List<Long> blNoIds = reqVO.getBlNoIds() == null ? new ArrayList<>() : reqVO.getBlNoIds();
        String cargoTypeStr = String.join(CommonConstants.CSV_DELIMITER, cargoTypes);
        String cntrStatuseStr = String.join(CommonConstants.CSV_DELIMITER, cntrStatuses);
        String cntrTypeStr = String.join(CommonConstants.CSV_DELIMITER, cntrTypes);
        String paymentStr = String.join(CommonConstants.CSV_DELIMITER, payments);
        String bkpCodeStr = String.join(CommonConstants.CSV_DELIMITER, bkpCodes);
        String blStatuseStr = String.join(CommonConstants.CSV_DELIMITER, blStatuses);
        String porCodeStr = String.join(CommonConstants.CSV_DELIMITER, porCodes);
        String polCodeStr = String.join(CommonConstants.CSV_DELIMITER, polCodes);
        String podCodeStr = String.join(CommonConstants.CSV_DELIMITER, podCodes);
        String delCodeStr = String.join(CommonConstants.CSV_DELIMITER, delCodes);
        String spaceOwnerStr = String.join(CommonConstants.CSV_DELIMITER, spaceOwners);
        String cntrOwnerStr = String.join(CommonConstants.CSV_DELIMITER, cntrOwners);
        String blNoIdStr =
            blNoIds.stream().filter(Objects::nonNull).map(String::valueOf).collect(Collectors.joining(","));

        String setParamUrl =
            String.format(SET_PARAM_URL, cargoTypeStr, cntrStatuseStr, cntrTypeStr, paymentStr, bkpCodeStr,
                blStatuseStr, porCodeStr, polCodeStr, podCodeStr, delCodeStr, spaceOwnerStr, cntrOwnerStr, blNoIdStr);
        return generateUrl + strParamUrl + booleanParamUrl + setParamUrl + getEnvUrl()
            + getFileTypeUrl(reqVO.getType());
    }

    private String getEnvUrl() {
        String env = activeProfile.split(CommonConstants.CSV_DELIMITER)[0];
        return "&tag=" + env;
    }

    private String getFileTypeUrl(String fileType) {
        if (DownloadFileTypeEnum.EXCEL.getValue().equals(fileType)) {
            return "&format=excel&extype=simple";
        } else if (DownloadFileTypeEnum.WORD.getValue().equals(fileType)) {
            return "&format=word";
        } else {
            return "&format=pdf";
        }
    }

    private void downLoadFile(HttpServletResponse response, String url, String fileType) {
        String fileSuffix;
        if (DownloadFileTypeEnum.EXCEL.getValue().equals(fileType)) {
            fileSuffix = ".xlsx";
        } else if (DownloadFileTypeEnum.WORD.getValue().equals(fileType)) {
            fileSuffix = ".doc";
        } else {
            fileSuffix = ".pdf";
        }
        String fileName = "cargo_manifest" + fileSuffix;
        String savePath = FileOperateUtil.getResourcePath() + fileName;
        FileOperateUtil.downloadWithCommonsIo(url, savePath);
        FileOperateUtil.downloadLargeFile(savePath, response);
    }

    private void setReportBody(CargoManifestReqDTO reqDto, CargoManifestInfo cargoManifestInfo, BlnoDO blnoDo,
        List<BlnoCntrDO> cntrList, List<BlnoFreightDO> freightList, List<BlnoScnDO> scnList, BlnoCargoDO cargo) {
        RowBlnoInfo rowBlnoInfo = buildRowBlnoInfo(blnoDo, cntrList, freightList);
        RowScnInfo rowScnInfo = buildRowScnInfo(reqDto, scnList);
        RowMarkAndCntrInfo rowMarkAndCntrInfo = buildRowMarkAndCntrInfo(blnoDo, cargo, cntrList);
        RowCargoInfo rowCargoInfo = buildRowCargoInfo(cargo);
        RowRemarkInfo rowRemarkInfo = buildRowRemarkInfo(blnoDo, cargo, reqDto.getExImType());

        cargoManifestInfo.setRowBlnoInfo(rowBlnoInfo);
        cargoManifestInfo.setRowScnInfo(rowScnInfo);
        cargoManifestInfo.setCommodityEn(cargo.getCommodityEn());
        cargoManifestInfo.setRowMarkAndCntrInfo(rowMarkAndCntrInfo);
        cargoManifestInfo.setRowCargoInfo(rowCargoInfo);
        cargoManifestInfo.setRowRemarkInfo(rowRemarkInfo);
    }

    private RowRemarkInfo buildRowRemarkInfo(BlnoDO blnoDo, BlnoCargoDO cargo, String exImType) {
        RowRemarkInfo rowRemarkInfo = new RowRemarkInfo();
        String podCode = blnoDo.getPodCode();
        String podTerminal = "";
        List<BlnoVslvoyDO> vslvoyList = blnoVslvoyService.getBlnoVslvoyBySiId(blnoDo.getId());
        VesselVoyRespDTO vesselResp = null;
        try {
            vesselResp = costChargeUtil.getVoyQueryRespDTO(podCode, exImType, vslvoyList);
        } catch (Exception e) {
            log.error("caught an exception on getVoyQueryRespDTO", e);
        }
        if (vesselResp != null) {
            podTerminal = vesselResp.getTerminalCode() == null ? "" : vesselResp.getTerminalCode();
        }
        rowRemarkInfo.setPodInfo("POD:" + podCode + "/" + podTerminal);

        String porCode = blnoDo.getPorCode();
        String delCode = blnoDo.getDelCode();
        PortListByCodeReqDTO portListByCodeReqDto = new PortListByCodeReqDTO();
        List<String> portCodeList = new ArrayList<>();
        portCodeList.add(porCode);
        portCodeList.add(delCode);
        portListByCodeReqDto.setPortCodeList(portCodeList);
        List<PortListByCodeRespDTO> portList = portService.listByCodeList(portListByCodeReqDto);
        String porNameEn = "";
        String delNameEn = "";
        if (!CollectionUtils.isEmpty(portList)) {
            for (PortListByCodeRespDTO portResp : portList) {
                if (porCode.equalsIgnoreCase(portResp.getPortCode())) {
                    porNameEn = portResp.getPortNameEn();
                }
                if (delCode.equalsIgnoreCase(portResp.getPortCode())) {
                    delNameEn = portResp.getPortNameEn();
                }
            }
        }
        rowRemarkInfo.setPorInfo("Place of receipt:" + porNameEn);
        rowRemarkInfo.setDelInfo("Place of delivery:" + delNameEn);
        String unNo = cargo.getUnNo() == null ? "" : cargo.getUnNo();
        String unNoAndImoInfo = getUnNoAndImoInfo(cargo, unNo);
        rowRemarkInfo.setUnNoAndImoInfo(unNoAndImoInfo);
        return rowRemarkInfo;
    }

    private String getUnNoAndImoInfo(BlnoCargoDO cargo, String unNo) {
        String unNoAndImoInfo = "";
        String cargoType = cargo.getCargoType();
        if (CargoTypeEnum.DG.getValue().equalsIgnoreCase(cargoType)
            || CargoTypeEnum.RD.getValue().equalsIgnoreCase(cargoType)) {
            unNoAndImoInfo = unNoAndImoInfo + "UNNO:" + unNo;
        }

        String imoClass = cargo.getImoClass() == null ? "" : cargo.getImoClass();
        if (CargoTypeEnum.RF.getValue().equalsIgnoreCase(cargoType)
            || CargoTypeEnum.RD.getValue().equalsIgnoreCase(cargoType)) {
            unNoAndImoInfo = unNoAndImoInfo + "CLASS:" + imoClass;
        }
        return unNoAndImoInfo;
    }

    private RowCargoInfo buildRowCargoInfo(BlnoCargoDO cargo) {
        String quantity = cargo.getQuantity() == null ? "" : String.valueOf(cargo.getQuantity());
        String packageDesc = cargo.getPackageDesc() == null ? "" : cargo.getPackageDesc();
        String grossWeight = cargo.getGrossWeight() == null ? "" : String.valueOf(cargo.getGrossWeight());
        String measurement = cargo.getMeasurement() == null ? "" : String.valueOf(cargo.getMeasurement());
        RowCargoInfo rowCargoInfo = new RowCargoInfo();
        rowCargoInfo.setQtyAndDesc("Qty:" + quantity + " " + packageDesc);
        rowCargoInfo.setGrossWeight("G.W:" + grossWeight + "KGS");
        rowCargoInfo.setMeasurement("Meas:" + measurement + "Cu.M");
        return rowCargoInfo;
    }

    private RowMarkAndCntrInfo buildRowMarkAndCntrInfo(BlnoDO blnoDo, BlnoCargoDO cargo, List<BlnoCntrDO> cntrList) {
        RowMarkAndCntrInfo rowMarkAndCntrInfo = new RowMarkAndCntrInfo();
        rowMarkAndCntrInfo.setMarks(cargo.getMarks());
        rowMarkAndCntrInfo.setShippingTerm(blnoDo.getShippingTerm());
        if (CollectionUtils.isEmpty(cntrList)) {
            return rowMarkAndCntrInfo;
        }

        Map<String, VgmDO> vgmDoMap = getVgmDoInfoMap(blnoDo, cntrList);
        List<String> cntrBaseInfoList = new ArrayList<>();
        for (BlnoCntrDO cntr : cntrList) {
            String cntrNo = cntr.getCntrNo() == null ? "" : cntr.getCntrNo();
            String sealNo = cntr.getSealNo() == null ? "" : cntr.getSealNo();
            String cntrSize = cntr.getCntrSize() == null ? "" : cntr.getCntrSize();
            String cntrType = cntr.getCntrType() == null ? "" : cntr.getCntrType();
            String cntrStatus = blnoDo.getCntrStatus() == null ? "" : blnoDo.getCntrStatus();
            String of = cntr.getOverForward() == null ? "" : String.valueOf(cntr.getOverForward());
            String oa = cntr.getOverAfter() == null ? "" : String.valueOf(cntr.getOverAfter());
            String ol = cntr.getOverLeft() == null ? "" : String.valueOf(cntr.getOverLeft());
            String or = cntr.getOverRight() == null ? "" : String.valueOf(cntr.getOverRight());
            String oh = cntr.getOverHeight() == null ? "" : String.valueOf(cntr.getOverHeight());
            String quantity = cntr.getQuantity() == null ? "" : String.valueOf(cntr.getQuantity());
            String grossWeight = cntr.getGrossWeight() == null ? "" : String.valueOf(cntr.getGrossWeight());
            String measurement = cntr.getMeasurement() == null ? "" : String.valueOf(cntr.getMeasurement());
            String sizeTypeSealOver = cntrNo + sealNo + cntrSize + cntrType + cntrStatus + " " + "O/F:" + of + ";"
                + "O/A:" + oa + ";" + "O/L:" + ol + ";" + "O/R:" + or + ";" + "O/H:" + oh + ";";
            String qtyGrossInfo = quantity + grossWeight + measurement;
            String vgmInfo = "VGM:";
            if (vgmDoMap.containsKey(cntrNo)) {
                VgmDO vgmDo = vgmDoMap.get(cntrNo);
                String weight = vgmDo.getWeight() == null ? "" : String.valueOf(vgmDo.getWeight());
                String typeCode = vgmDo.getTypeCode() == null ? "" : vgmDo.getTypeCode();
                vgmInfo = vgmInfo + weight + typeCode;
            }
            String cntrBaseInfoStr = sizeTypeSealOver + CommonConstants.CSV_DELIMITER + qtyGrossInfo
                + CommonConstants.CSV_DELIMITER + vgmInfo;
            cntrBaseInfoList.add(cntrBaseInfoStr);
        }
        rowMarkAndCntrInfo.setCntrBaseInfos(String.join(CommonConstants.CSV_DELIMITER, cntrBaseInfoList));
        return rowMarkAndCntrInfo;
    }

    private RowScnInfo buildRowScnInfo(CargoManifestReqDTO reqDto, List<BlnoScnDO> scnList) {
        RowScnInfo rowScnInfo = new RowScnInfo();
        String shipperInfo = "";
        String consigneeInfo = "";
        String notifyInfo = "";
        if (reqDto.getReplaceScnSinolines()) {
            shipperInfo = SINOLINES;
            consigneeInfo = SINOLINES;
            notifyInfo = SINOLINES;
        } else {
            for (BlnoScnDO scn : scnList) {
                String companyName = scn.getCompanyName() == null ? "" : scn.getCompanyName();
                String companyAddress = scn.getCompanyAddress() == null ? "" : scn.getCompanyAddress();
                String city = scn.getCity() == null ? "" : scn.getCity();
                String zipCode = scn.getZipCode() == null ? "" : scn.getZipCode();
                String provinceCode = scn.getProvinceCode() == null ? "" : scn.getProvinceCode();
                String countryCode = scn.getCountryCode() == null ? "" : scn.getCountryCode();
                String telephone = scn.getTelephone() == null ? "" : scn.getTelephone();
                String fax = scn.getFax() == null ? "" : scn.getFax();
                String resp = companyName + " " + companyAddress + " " + city + " " + zipCode + " " + provinceCode + " "
                    + countryCode + " " + telephone + " " + fax;
                if (PartyTypeCodeEnum.SHIPPER.getValue().equalsIgnoreCase(scn.getPartyTypeCode())) {
                    shipperInfo = resp;
                }
                if (PartyTypeCodeEnum.SHIPPER.getValue().equalsIgnoreCase(scn.getPartyTypeCode())) {
                    consigneeInfo = resp;
                }
                if (PartyTypeCodeEnum.SHIPPER.getValue().equalsIgnoreCase(scn.getPartyTypeCode())) {
                    notifyInfo = resp;
                }
            }
        }

        rowScnInfo.setShipperInfo("SHIPPER:" + shipperInfo);
        rowScnInfo.setConsigneeInfo("CONSIGNEE:" + consigneeInfo);
        rowScnInfo.setNotifyInfo("NOTIFY:" + notifyInfo);
        return rowScnInfo;
    }

    private RowBlnoInfo buildRowBlnoInfo(BlnoDO blnoDo, List<BlnoCntrDO> cntrList, List<BlnoFreightDO> freightList) {
        RowBlnoInfo rowBlnoInfo = CargoManifestConvert.INSTANCE.convert(blnoDo);

        // 设置cntr信息F/GP20'1
        StringBuilder cntrSb = new StringBuilder();
        if (!CollectionUtils.isEmpty(cntrList)) {
            Map<String,
                Long> cntrMap = cntrList.stream().filter(Objects::nonNull)
                    .filter(cntr -> cntr.getCntrType() != null && cntr.getCntrSize() != null).collect(
                        Collectors.groupingBy(cntr -> cntr.getCntrType() + cntr.getCntrSize(), Collectors.counting()));
            for (Map.Entry<String, Long> entry : cntrMap.entrySet()) {
                cntrSb.append(blnoDo.getCntrStatus()).append(CommonConstants.UNDERSCORE).append(entry.getKey())
                    .append(CommonConstants.SINGLE_QUOTE).append(entry.getValue()).append(CommonConstants.SEMICOLON);
            }
            cntrSb.deleteCharAt(cntrSb.length() - 1);
        }
        rowBlnoInfo.setCntrStr(cntrSb.toString());

        // 设置海运费相关
        setRowBlnoFreight(rowBlnoInfo, freightList);

        return rowBlnoInfo;
    }

    private void setRowBlnoFreight(RowBlnoInfo rowBlnoInfo, List<BlnoFreightDO> freightList) {
        String freightPayment = "";
        if (CollectionUtils.isEmpty(freightList)) {
            return;
        }
        StringBuilder prepaidSurchargeSb = new StringBuilder();
        for (BlnoFreightDO freight : freightList) {
            if (CommonConstants.FRT.equalsIgnoreCase(freight.getChargeCode())) {
                String payment = freight.getPayment();
                freightPayment = PaymentEnum.getNameByValue(payment);
            }
            if (PaymentEnum.P.getValue().equalsIgnoreCase(freight.getPayment())) {
                prepaidSurchargeSb.append(freight.getChargeCode()).append(CommonConstants.CSV_DELIMITER);
            }
        }
        if (prepaidSurchargeSb.length() > 0) {
            prepaidSurchargeSb.deleteCharAt(prepaidSurchargeSb.length() - 1);
            prepaidSurchargeSb.append(":FREIGHT");
            rowBlnoInfo.setPrepaidSurcharge(prepaidSurchargeSb.toString());
        }
        rowBlnoInfo.setFreightPayment("FREIGHT " + freightPayment);

        Map<String, List<BlnoFreightDO>> freightGroupMap =
            freightList.stream().filter(Objects::nonNull).collect(Collectors.groupingBy(this::buildKey));
        Map<String, BigDecimal> freightGroupAmountMap =
            freightList.stream().filter(Objects::nonNull).collect(Collectors.groupingBy(this::buildKey,
                Collectors.reducing(BigDecimal.ZERO, BlnoFreightDO::getAmount, BigDecimal::add)));
        List<String> freightStrList = new ArrayList<>();
        for (Map.Entry<String, List<BlnoFreightDO>> entry : freightGroupMap.entrySet()) {
            BlnoFreightDO freightTmp = entry.getValue().get(0);
            String currency = freightTmp.getCurrency();
            BigDecimal amount = freightGroupAmountMap.get(entry.getKey());
            String amountStr;
            if (CurrencyEnum.JPY.getValue().equalsIgnoreCase(currency)) {
                amountStr = amount.setScale(0, RoundingMode.HALF_UP).toString();
            } else {
                amountStr = amount.setScale(2, RoundingMode.HALF_UP).toString();
            }
            String freightStr =
                freightTmp.getChargeCode() + " " + amountStr + " " + currency + " " + freightTmp.getPayment();
            freightStrList.add(freightStr);
        }

        rowBlnoInfo.setFreightStrs(String.join(CommonConstants.CSV_DELIMITER, freightStrList));
    }

    private void setReportHeader(CargoManifestRespDTO respDto, CargoManifestReqDTO reqDto, List<BlnoDO> blnoDoList) {
        setHeadPolAndPod(respDto, reqDto, blnoDoList);
        String nowStr = DateUtils.getCurrentTimeStr(REPORT_TIME_PATTERN);
        List<BlnoVslvoyDO> vslvoyList = blnoVslvoyService.getBlnoVslvoyBySiId(blnoDoList.get(0).getId());
        String vesselCode = null;
        String voyage = null;
        if (!StringUtils.isEmpty(reqDto.getVesselCode())) {
            vesselCode = reqDto.getVesselCode();
            voyage = reqDto.getVoyage();
        } else {
            String exImType = reqDto.getExImType();
            for (BlnoVslvoyDO vslvoy : vslvoyList) {
                if (ExImTypeEnum.IM.getValue().equalsIgnoreCase(exImType)) {
                    if (vslvoy.getPodCode().equalsIgnoreCase(reqDto.getCurrentPort())) {
                        vesselCode = vslvoy.getVesselCode();
                        voyage = vslvoy.getVoyageIm();
                    }
                } else {
                    if (vslvoy.getPolCode().equalsIgnoreCase(reqDto.getCurrentPort())) {
                        vesselCode = vslvoy.getVesselCode();
                        voyage = vslvoy.getVoyageEx();
                    }
                }
            }
        }

        // 根据vesselCode查询船名
        List<String> vesselCodes = new ArrayList<>();
        vesselCodes.add(vesselCode);
        Map<String, VesselNameDataRespDTO> respDtoMap = new HashMap<>();
        try {
            respDtoMap = vesselTransferService.getVesselDataByCodes(vesselCodes);
        } catch (Exception e) {
            log.error("caught an exception on getVesselDataByCodes", e);
        }
        String vesselNameEn = respDtoMap.getOrDefault(vesselCode, new VesselNameDataRespDTO()).getVesselNameEn();

        // 查询开航时间
        VesselVoyRespDTO vesselResp = null;
        try {
            vesselResp = costChargeUtil.getVoyQueryRespDTO(reqDto.getCurrentPort(), reqDto.getExImType(), vslvoyList);
        } catch (Exception e) {
            log.error("caught an exception on getVoyQueryRespDTO", e);
        }
        String departureTime = null;
        if (vesselResp != null) {
            if (ExImTypeEnum.IM.getValue().equalsIgnoreCase(reqDto.getExImType())) {
                departureTime = vesselResp.getAta() == null ? vesselResp.getPta() : vesselResp.getAta();
            } else {
                departureTime = vesselResp.getAtd() == null ? vesselResp.getPtd() : vesselResp.getAtd();
            }
        }
        respDto.setReportTime(nowStr);
        respDto.setVesselNameEn(vesselNameEn);
        respDto.setVoyage(voyage);
        respDto.setDepartureTime(departureTime);
    }

    private void setHeadPolAndPod(CargoManifestRespDTO respDto, CargoManifestReqDTO reqDto, List<BlnoDO> blnoDoList) {
        Set<Long> ids = blnoDoList.stream().map(BlnoDO::getId).collect(Collectors.toSet());
        List<BlnoVslvoyDO> blnoVslvoyDOList = blnoVslvoyService.getBlnoVslvoyBySiIdList(ids);
        String exImType = reqDto.getExImType();
        Set<String> polSet = new HashSet<>();
        Set<String> podSet = new HashSet<>();
        String currentPort = reqDto.getCurrentPort();
        for (BlnoVslvoyDO vslvoy : blnoVslvoyDOList) {
            if (ExImTypeEnum.IM.getValue().equalsIgnoreCase(exImType)) {
                if (vslvoy.getPodCode().equals(currentPort)) {
                    polSet.add(vslvoy.getPolCode());
                    podSet.add(vslvoy.getPodCode());
                    break;
                }
            } else {
                if (vslvoy.getPolCode().equals(currentPort)) {
                    polSet.add(vslvoy.getPolCode());
                    podSet.add(vslvoy.getPodCode());
                    break;
                }
            }
        }

        respDto.setPolCode(CollectionUtils.isEmpty(polSet) ? "" : String.join(",", polSet));
        respDto.setPodCode(CollectionUtils.isEmpty(podSet) ? "" : String.join(",", podSet));
    }

    private void checkQueryReqVo(CargoManifestReqDTO reqVO) {
        if (StringUtils.isEmpty(reqVO.getBlNo()) && StringUtils.isEmpty(reqVO.getVesselCode())
            && ObjectUtils.isEmpty(reqVO.getBlNoIds())) {
            throw ServiceExceptionUtil.exception(CargoManifestErrorCodeConstants.BLNO_VESSEL_HAS_LEAST_ONE);
        }
    }

    private List<BlnoDO> queryBlnoDo(CargoManifestReqDTO reqVO) {
        List<BlnoDO> blnoDoList = new ArrayList<>();
        try {
            if (ExImTypeEnum.IM.getValue().equalsIgnoreCase(reqVO.getExImType())) {
                blnoDoList = cargoManifestMapper.queryCargoManifestIm(reqVO);
            } else {
                blnoDoList = cargoManifestMapper.queryCargoManifestEx(reqVO);
            }
        } catch (Exception e) {
            log.error("caught an exception on queryBlnoDo", e);
        }
        return blnoDoList;
    }

    private CargoManifestQueryRespVO queryCargoManifestSi(CargoManifestReqDTO reqVO) {
        List<BlnoDO> blnoDoList = queryBlnoDo(reqVO);
        if (CollectionUtils.isEmpty(blnoDoList)) {
            log.error("query cargo manifest resp null");
            return new CargoManifestQueryRespVO();
        }
        Set<Long> ids = blnoDoList.stream().map(BlnoDO::getId).collect(Collectors.toSet());
        List<BlnoCntrDO> cntrDOList = blnoCntrService.getBlnoCntrByBlnoIds(ids);
        List<BlnoCargoDO> blnoCargoDOList = blnoCargoService.getBlnoCargoByBlnoIds(ids);
        List<BlnoFreightDO> blnoFreightDOList = blnoFreightService.getBlnoFreightByBlnoIdList(ids);
        return buildCargoManifestResp(blnoDoList, cntrDOList, blnoCargoDOList, blnoFreightDOList);
    }

    private CargoManifestQueryRespVO queryCargoManifestManifest(CargoManifestQueryReqVO reqVO) {
        throw ServiceExceptionUtil.exception(CargoManifestErrorCodeConstants.MANIFEST_CURRENTLY_NOT_SUPPORTED);
    }

    private CargoManifestQueryRespVO buildCargoManifestResp(List<BlnoDO> blnoDoList, List<BlnoCntrDO> cntrDOList,
        List<BlnoCargoDO> blnoCargoDOList, List<BlnoFreightDO> blnoFreightDOList) {
        Set<String> cargoTypes = blnoCargoDOList.stream().map(BlnoCargoDO::getCargoType).collect(Collectors.toSet());
        Set<String> cntrStatuses = blnoDoList.stream().map(BlnoDO::getCntrStatus).collect(Collectors.toSet());
        Set<String> cntrTypes = cntrDOList.stream().map(BlnoCntrDO::getCntrType).collect(Collectors.toSet());
        Set<String> payments = blnoFreightDOList.stream().map(BlnoFreightDO::getPayment).collect(Collectors.toSet());
        Set<String> bkpCodes = blnoDoList.stream().map(BlnoDO::getBkpCode).collect(Collectors.toSet());
        Set<String> blStatuses = blnoDoList.stream().map(BlnoDO::getSplitCombineType).collect(Collectors.toSet());
        Set<String> porCodes = blnoDoList.stream().map(BlnoDO::getPorCode).collect(Collectors.toSet());
        Set<String> polCodes = blnoDoList.stream().map(BlnoDO::getPolCode).collect(Collectors.toSet());
        Set<String> podCodes = blnoDoList.stream().map(BlnoDO::getPodCode).collect(Collectors.toSet());
        Set<String> delCodes = blnoDoList.stream().map(BlnoDO::getDelCode).collect(Collectors.toSet());
        Set<String> spaceOwners = blnoDoList.stream().map(BlnoDO::getSpaceOwner).collect(Collectors.toSet());
        Set<String> cntrOwners = cntrDOList.stream().map(BlnoCntrDO::getCntrOwner).collect(Collectors.toSet());

        CargoManifestQueryRespVO resp = new CargoManifestQueryRespVO();
        resp.setCargoTypes(cargoTypes);
        resp.setCntrStatuses(cntrStatuses);
        resp.setCntrTypes(cntrTypes);
        resp.setPayments(payments);
        resp.setBkpCodes(bkpCodes);
        resp.setBlStatuses(blStatuses);
        resp.setPorCodes(porCodes);
        resp.setPolCodes(polCodes);
        resp.setPodCodes(podCodes);
        resp.setDelCodes(delCodes);
        resp.setSpaceOwners(spaceOwners);
        resp.setCntrOwners(cntrOwners);
        return resp;
    }

    private List<BlnoCntrDO> getBlnoCntrByBlnoIds(Collection<Long> ids, Set<String> cntrTypes, Set<String> cntrOwners) {
        LambdaQueryWrapperX<BlnoCntrDO> queryWrapperX = new LambdaQueryWrapperX<>();
        queryWrapperX.in(BlnoCntrDO::getBlnoId, ids).in(BlnoCntrDO::getCntrType, cntrTypes).in(BlnoCntrDO::getCntrOwner,
            cntrOwners);
        return blnoCntrMapper.selectList(queryWrapperX);
    }

    private List<BlnoFreightDO> getBlnoFreightByBlnoIds(Collection<Long> ids, CargoManifestReqDTO reqDto) {
        LambdaQueryWrapperX<BlnoFreightDO> queryWrapperX = new LambdaQueryWrapperX<>();
        queryWrapperX.in(BlnoFreightDO::getBlnoId, ids);
        boolean showFreight = reqDto.getShowFreight();
        boolean includeAllChange = reqDto.getIncludeAllChange();
        Set<String> nonDeclaredCode = new HashSet<>();
        nonDeclaredCode.add(CommonConstants.OFA);
        nonDeclaredCode.add(CommonConstants.EMC);
        if (!showFreight) {
            return new ArrayList<>();
        } else {
            queryWrapperX.inIfPresent(BlnoFreightDO::getPayment, reqDto.getPayments());
            if (!includeAllChange) {
                queryWrapperX.notIn(BlnoFreightDO::getPayment, nonDeclaredCode);
            }
        }
        return blnoFreightMapper.selectList(queryWrapperX);
    }

    private String buildKey(BlnoFreightDO freight) {
        StringBuilder sb = new StringBuilder();
        if (freight.getChargeCode() != null) {
            sb.append(freight.getChargeCode());
        }

        if (freight.getPer() != null) {
            sb.append(freight.getPer());
        }

        if (freight.getCntrSize() != null) {
            sb.append(freight.getCntrSize());
        }

        if (freight.getCntrType() != null) {
            sb.append(freight.getCntrType());
        }

        if (freight.getPayment() != null) {
            sb.append(freight.getPayment());
        }

        if (freight.getCurrency() != null) {
            sb.append(freight.getQuantity());
        }

        return sb.toString();
    }

    private Map<String, VgmDO> getVgmDoInfoMap(BlnoDO blnoDO, List<BlnoCntrDO> blnoList) {
        List<String> cntrNoList = blnoList.stream().map(BlnoCntrDO::getCntrNo).distinct().collect(Collectors.toList());
        VgmQueryInfoVO vgmQueryInfoVo = new VgmQueryInfoVO();
        vgmQueryInfoVo.setVoyage(blnoDO.getBkgVoyage());
        vgmQueryInfoVo.setVoyage(blnoDO.getBkgVoyage());
        vgmQueryInfoVo.setCntrNo(cntrNoList);
        List<VgmDO> vgmDOList = vgmService.getVgmInfo(vgmQueryInfoVo);
        return vgmDOList.stream().filter(Objects::nonNull).filter(vgm -> vgm.getCntrNo() != null)
            .collect(Collectors.toMap(VgmDO::getCntrNo, vgm -> vgm, (existing, replacement) -> replacement // 重复key时覆盖
            ));
    }
}
